package dnl.dtools.grm.ssh;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.math.RandomUtils;

import ajul.lang.Args;
import ch.ethz.ssh2.SCPClient;
import dnl.dtools.grm.Resource;
import dnl.dtools.grm.ssh.scrapers.ListScraper;
import dnl.dtools.grm.ssh.scrapers.ListScraper.SimpleFileInfo;
import dnl.logging.LightLog;

/**
 * Represents an entry on a remote file system that is accessed via SSH.
 * 
 * @author Daniel Or
 * 
 */
public class GanymedSshEntry extends Resource {

	private SshSession sshSession;

	private String remotePath;

	// private boolean exists;

	private boolean isDirectory;

	private boolean isArchive;

	private long size;

	private long lastModifTime;

	private GanymedSshEntry parent;

	public GanymedSshEntry(URL url, SshSession sshSess)
			throws URISyntaxException {
		this(url, sshSess, null);
	}

	private GanymedSshEntry(URL url, SshSession sshSess, GanymedSshEntry parent)
	throws URISyntaxException {
		super(url);
		Args.assertNotNull(sshSess);
		this.sshSession = sshSess;
		this.remotePath = url.getFile();
		if (this.remotePath.length() > 1 && this.remotePath.charAt(1) == '~') {
			this.remotePath = this.remotePath.substring(1);
		}
		this.parent = parent;
		sync();
	}

	private void resolveParent(GanymedSshEntry gse) {
		int ind = this.url.toString().lastIndexOf('/');
		if (ind != -1 && ind > 0) {
			String parentUrlStr = null;
			if(url.getFile().equals("/"))
				return;
			parentUrlStr = this.url.toString().substring(0, ind);
			if(url.getFile().lastIndexOf('/') == 0){
				parentUrlStr += "/";
			}
			
			URL parentUrl = null;
			try {
				parentUrl = new URL(parentUrlStr);
				GanymedSshEntry parent = new GanymedSshEntry(parentUrl,
						this.sshSession);
				fillMissingDataByProbing(gse);
				this.parent = parent;
			} catch (MalformedURLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (URISyntaxException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	public SshSession getSshSession() {
		return sshSession;
	}

	public String getRemotePath() {
		return remotePath;
	}

	private void sync(){
		if(!sshSession.isConnected()){
			try {
				sshSession.connect();
				fillMissingDataByProbing(this);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	private SshCommandResult executeCommand(String command) {
		try {
			return sshSession.executeCommand(command);
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
	}

	@Override
	public boolean delete() throws IOException {
		SshCommandResult r = null;
		if (isDirectory()) {
			r = executeCommand("rm -fr " + this.remotePath);
		} else {
			r = executeCommand("rm " + this.remotePath);
		}
		return hasErrors(r);
	}

	@Override
	public boolean exists() {
		return true;
	}

	@Override
	public Resource getChild(String name) {
		URL newUrl;
		try {
			newUrl = new URL(this.getUrl().toString() + "/" + name);
			return new GanymedSshEntry(newUrl, this.sshSession, this);
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public InputStream getInputStream() {
		try {
			SCPClient scpClient = sshSession.getSshConection()
					.createSCPClient();
			File tmpDir = getTmpDir();
			if (tmpDir.exists() && tmpDir.isDirectory()) {
				// assert no other file with same name
			} else {
				FileUtils.forceMkdir(tmpDir);
			}
			scpClient.get(this.remotePath, tmpDir.getAbsolutePath());
			File f = new File(tmpDir.getAbsoluteFile() + File.separator
					+ this.getName());
			return new FileInputStream(f);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	private File getTmpDir(){
		File tmpDir = null;
		while(tmpDir == null || tmpDir.exists()){
			tmpDir = new File("./tmp_scp_files/tmp_dir_"+RandomUtils.nextInt());
		}
		try {
			FileUtils.forceMkdir(tmpDir);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
		return tmpDir;
	}

	@Override
	public long getLastModified() {
		return lastModifTime;
	}

	@Override
	public OutputStream getOutputStream() {
		File tmpDir = getTmpDir();
		File f = new File(tmpDir, this.getName());
		try {
			SshOutputStream out = new SshOutputStream(f);
			return out;
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
	}

	@Override
	public Resource getParent() {
		if(parent == null && !isRoot()) {
			resolveParent(this);
		}
		return parent;
	}

	@Override
	public long getSize() {
		sync();
		return size;
	}

	@Override
	public boolean isArchive() {
		return isArchive;
	}

	@Override
	public boolean isArchiveEntry() {
		return false;
	}		
	
	@Override
	public boolean isDirectory() {
		//sync();
		return isDirectory;
	}

	@Override
	public boolean isHidden() {
		return false;
	}
	
	@Override
	public boolean isLocal() {
		return false;
	}

	@Override
	public Resource[] list() {
		SshCommandResult r = executeCommand("ls --full-time -F '"
				+ this.remotePath+"'");
		if (r == null)
			return null;
		String out = r.getSysout();
		ListScraper ls = new ListScraper();
		SimpleFileInfo[] infos = ls.scrape(out);
		GanymedSshEntry[] entries = new GanymedSshEntry[infos.length];
		for (int i = 0; i < entries.length; i++) {
			entries[i] = (GanymedSshEntry) getChild(infos[i].getName());
			entries[i].isDirectory = infos[i].isDirectory();
			entries[i].lastModifTime = infos[i].getDate();
			entries[i].size = infos[i].getSize();
		}
		return entries;
	}

	@Override
	public boolean mkDir() throws IOException {
		// TODO: add assertion that it doesn't exists already.
		SshCommandResult r = executeCommand("mkdir '" + this.remotePath+"'");
		return hasErrors(r);
	}

	@Override
	public boolean renameTo(Resource dest) throws IOException {
		GanymedSshEntry newLoc = (GanymedSshEntry) dest;
		SshCommandResult r = executeCommand("mv '" + this.remotePath + "' '"
				+ newLoc.remotePath+"'");
		return hasErrors(r);
	}
	
	public boolean isRoot(){
		return this.remotePath.equals("/");
	}

	private void fillMissingDataByProbing(GanymedSshEntry gse) {
		SshCommandResult r = executeCommand("ls --full-time -F -d '"
				+ this.remotePath+"'");
		if (r == null)
			return;
		String out = r.getSysout();
		ListScraper ls = new ListScraper();
		SimpleFileInfo[] infos = ls.scrape(out);

		//gse = (GanymedSshEntry) getChild(infos[0].getName());
		gse.isDirectory = infos[0].isDirectory();
		gse.lastModifTime = infos[0].getDate();
		gse.size = infos[0].getSize();
	}

	@Override
	public String toString() {
		return "GanymedSshEntry{"+remotePath+"}";
	}

	/**
	 * A utility method that delegates SshCommandResult.hasErrors() in order to
	 * log any errors.
	 * 
	 * @param result
	 * @return
	 */
	private boolean hasErrors(SshCommandResult result) {
		if (result.hasErrors()) {
			// TODO: keep errors somewhere so the UI will be able to get em.
			return true;
		}
		return false;
	}
	
	private class SshOutputStream extends FileOutputStream {

		private File file;
		public SshOutputStream(File file) throws FileNotFoundException {
			super(file);
			this.file = file;
		}

		@Override
		public void close() throws IOException {
			super.close();
			try {
				LightLog.debug(
						"Using SCP client to put tmp file '"+
						file.getAbsolutePath()+
						"' in remote dir: '"+remotePath+"'");
				SCPClient scpClient = sshSession.getSshConection()
						.createSCPClient();
				GanymedSshEntry parent = (GanymedSshEntry) getParent();
				scpClient.put(file.getAbsolutePath() , parent.getRemotePath());
				//FileUtils.forceDelete(file.getParentFile());
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		
	}

}
